﻿using System;
using System.Web;
using Autofac;
using Autofac.Core;
using Autofac.Integration.Web;
using WebFormsMvp.Binder;

namespace WebFormsMvp.Contrib.Autofac
{
    /// <summary>
    /// A custom WebForms MVP presenter factory which uses Autofac to do type resolution
    /// </summary>
    /// <remarks>
    /// <example>
    /// <code>
    /// var builder = new ContainerBuilder();
    /// builder.RegisterPresenters(Assembly.GetExecutingAssembly());
    /// WebFormsMvp.Binder.PresenterBinder.Factory = new AutofacPresenterFactory(new ContainerProvider(builder.Build()));
    /// </code>
    /// </example>
    /// </remarks>
    public class AutofacPresenterFactory : IPresenterFactory
    {
        private readonly IContainerProvider _container;
        private readonly IPresenterIdentificationStrategy _discoveryStrategy;

        /// <summary>
        /// Initializes a new instance of the <see cref="AutofacPresenterFactory"/> class using the <see cref="DefaultPresenterIdentificationStrategy"/>
        /// </summary>
        /// <param name="containerProvider">The container provider.</param>
        public AutofacPresenterFactory(IContainerProvider containerProvider)
            : this(containerProvider, new DefaultPresenterIdentificationStrategy())
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="AutofacPresenterFactory"/> class.
        /// </summary>
        /// <param name="containerProvider">The container provider.</param>
        /// <param name="discoveryStrategy">The discovery strategy.</param>
        public AutofacPresenterFactory(IContainerProvider containerProvider,
                                       IPresenterIdentificationStrategy discoveryStrategy)
        {
            if (containerProvider == null)
                throw new ArgumentNullException("containerProvider", "Must provide an Autofac container provider");

            _container = containerProvider;
            _discoveryStrategy = discoveryStrategy;
        }

        #region IPresenterFactory Members

        /// <summary>
        /// Creates a new instance of the specific presenter type, for the specified
        /// view type and instance.
        /// </summary>
        /// <param name="presenterType">The type of presenter to create.</param>
        /// <param name="viewType">The type of the view as defined by the binding that matched.</param>
        /// <param name="viewInstance">The view instance to bind this presenter to.</param>
        /// <returns>An instantiated presenter.</returns>
        public IPresenter Create(Type presenterType, Type viewType, IView viewInstance)
        {
            Service service = _discoveryStrategy.ServiceForPresenterType(presenterType);
            object presenter;
            if (_container.RequestLifetime.TryResolveService(service,
                                                             new[] {new LooselyTypedParameter(viewType, viewInstance)},
                                                             out presenter))
                return (IPresenter) presenter;

            throw new HttpException("Unable to resolve type " + presenterType.FullName + " using service " +
                                    service.Description);
        }

        /// <summary>
        /// Releases the specified presenter from any of its lifestyle demands.
        /// This method's activities are implementation specific - for example,
        /// an IoC based factory would return the presenter to the container.
        /// </summary>
        /// <param name="presenter">The presenter to release.</param>
        public void Release(IPresenter presenter)
        {
            //not required. Let Autofac release the tiems
        }

        #endregion
    }

    internal class LooselyTypedParameter : ConstantParameter
    {
        public LooselyTypedParameter(Type type, object value)
            : base(value, pi => pi.ParameterType.IsAssignableFrom(type))
        {
            if (type == null)
            {
                throw new ArgumentNullException("type", "Type must be provided");
            }

            Type = type;
        }

        public Type Type { get; private set; }
    }
}